﻿#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>



//<1>sizeof
//int main()
//{
//	int a = 0;
//	printf("%zd\n", sizeof(a));//sizeof计算的是变量所占内存的大小
//	printf("%zd\n", sizeof(int));//把类型放在sizeof中去计算，计算的也是类型创建的变量的所占内存的大小
//	printf("%zd\n", sizeof a);//变量名外部的括号可以去掉，不影响计算的大小，恰恰也说明了sizeof是操作符，而不是函数，函数的使用需要：函数名 + ()
//
//	return 0;
//}

//sizeof中的表达式是不会参与计算的
//int main()
//{
//	int a = 5;
//	short s = 3;
//	printf("%zd\n", sizeof(s = a + 2));//sizeof的计算结果取决于s的类型，最终的指向对象的类型
//					//sizeof的返回值是size_t，用%zd打印
//	printf("%d\n", s);//但是sizeof的表达式不会计算，所以s仍然等于3
//
//	return 0;
//}
//为什么sizeof的表达式不会参与计算呢？
//因为表达式的计算是在运行时期，而sizeof在编译时期就已经完成操作了，里面的表达式也跟着销毁，不会留到运行时期



//<2>strlen
		//strlen是计算字符串长度的，有以下几种不同方式存放字符串长度并计算的情形
//int main()
//{
//	//直接用strlen存放并计算
//	int len = strlen("abc");
//	printf("%d", len);
//
//	return 0;
//}

//int main()
//{
//	//用指针存放地址再打印
//	const char* p1 = "abc";
//	printf("%d", strlen(p1));
//
//	return 0;
//}

//int main()
//{
//	//用数组存放一整条字符串
//	char arr[] = "abc";
//	printf("%d", strlen(arr));
//
//
//	return 0;
//}

//用数组存放单个字符
//int main()
//{
//	char arr[] = { 'a','b','c' };
//	printf("%d", strlen(arr));//打印的是伪随机值，因为单个字符后面没有\0，strlen遇到\0才会停止计算
//	
//	return 0;
//}



//对数组名arr的理解
//arr：数组名
//arr：数组首元素的地址     ，除了两种特殊情况外
//sizeof(数组名)，arr是整个数组，计算的是整个数组的大小
//&数组名，取了整个数组的地址

//int main()
//{
//	int a[] = { 1,2,3,4 };//a数组中有4个元素
//	printf("%zd\n", sizeof(a));//是sizeof(数组名)的情况，计算的是整个数组的大小，答案是16个字节
//	printf("%zd\n", sizeof(a + 0));//不是&，也不是sizeof(数组名)的情况（注意：只要sizeof括号中不是只有数组名，就不是特殊情况），所以这边数组名是数组首元素的地址
//									//不论地址会不会计算，就是地址，地址的大小取决于环境，是4\8个字节
//	printf("%zd\n", sizeof(*a));//这里的数组名表示数组首元素的地址，再解引用，就是数组中第一个元素，就是4个字节
//	printf("%zd\n", sizeof(a + 1));//这里的数组名表示数组首元素的地址，是地址就是指针，类型是int*，4\8个字节
//	printf("%zd\n", sizeof(a[1]));//数组第二个元素，4个字节
//	printf("%zd\n", sizeof(&a));//这里的数组名是取整个数组的地址，是地址就是4\8个字节
//	printf("%zd\n", sizeof(*&a));//先取整个数组的地址，再解引用，就是计算整个数组的大小，16个字节
//	printf("%zd\n", sizeof(&a + 1));//先取整个数组的地址，不论有没有计算，都是地址，就是4\8个字节
//	printf("%zd\n", sizeof(&a[0]));//地址，4\8个字节
//	printf("%zd\n", sizeof(&a[0] + 1));//地址，4\8个字节
//
//	return 0;
//}

//int main()
//{
//
//	char arr[] = { 'a','b','c','d','e','f' };
//	printf("%zd\n", sizeof(arr));//6 -- 计算的是整个数组所占内存的大小，就是6个char元素的大小
//								//注：越界访问的造成需要两个条件：1，函数为printf或strlen	2，没有\0
//	printf("%zd\n", sizeof(arr + 0));//地址
//	printf("%zd\n", sizeof(*arr));//元素
//	printf("%zd\n", sizeof(arr[1]));//元素
//	printf("%zd\n", sizeof(&arr));//地址
//	printf("%zd\n", sizeof(&arr + 1));//地址
//	printf("%zd\n", sizeof(&arr[0] + 1));//地址
//	//注：内存中的每块空间都有分配地址，每个地址的大小都是一样的，不会因为地址的类型不同，而发生大小的变化，所以指针也是一样，不会因为类型不同而发生大小的变化
//	return 0;
//}

#include <string.h>
//int main()
//{
//	char arr[] = { 'a','b','c','d','e','f' };
//	//strlen()函数的本质是把一个地址传入，然后向地址后访问并计算
//	printf("%d\n", strlen(arr));//把arr首元素的地址传入，向后访问并计算，但因为没有\0，所以是伪随机值
//	printf("%d\n", strlen(arr + 0));//首元素的地址 + 0；还是首元素的地址，向后访问，仍然是伪随机值
//	//printf("%d\n", strlen(*arr));//首元素地址解引用，是首元素，因为strlen的语法，是把'a'的ASCII码值97传到strlen中进行向后访问并计算，但strlen接收的是野指针，会程序崩溃
//	//printf("%d\n", strlen(arr[1]));//第二个元素，把'b'的ASCII码值的98传入到strlen中，向后访问并计算，但strlen接收的是野指针，会程序崩溃
//	printf("%d\n", strlen(&arr));//整个数组的地址，但也是从首元素地址开始的，向后访问并计算，是伪随机值
//	printf("%d\n", strlen(&arr + 1));//跳过一个数组的地址，向后访问并计算，是 伪随机值 - 6
//	printf("%d\n", strlen(&arr[0] + 1));//跳过一个元素的地址，向后访问并计算，是 伪随机值 - 1
//
//
//	return 0;
//}


//int main()
//{
//	char arr[] = "abcdef";//数组中的元素有7个
//	//注：只有字符串才有\0，其他的都没有
//	printf("%d\n", sizeof(arr));
//	printf("%d\n", sizeof(arr + 0));
//	printf("%d\n", sizeof(*arr));
//	printf("%d\n", sizeof(arr[1]));
//	printf("%d\n", sizeof(&arr));
//	printf("%d\n", sizeof(&arr + 1));
//	printf("%d\n", sizeof(&arr[0] + 1));
//
//
//	return 0;
//}


//int main()
//{
//
//	char arr[] = "abcdef";
//	printf("%d\n", strlen(arr));
//	printf("%d\n", strlen(arr + 0));
//	//printf("%d\n", strlen(*arr));//野指针，程序崩溃
//	//printf("%d\n", strlen(arr[1]));//野指针，程序崩溃
//	printf("%d\n", strlen(&arr));//取的是整个数组的地址，当然也是从首元素的地址开始访问并向后计算的，直到遇到\0停止
//	printf("%d\n", strlen(&arr + 1));//地址跳过一个数组，跳过的地址在字符串自带的\0后，，伪随机值
//	printf("%d\n", strlen(&arr[0] + 1));
//
//
//	return 0;
//}


//int main()
//{
//	char* p = "abcdef";
//	printf("%d\n", sizeof(p));//p是指针，指针就是地址，地址就是4/8个字节
//	printf("%d\n", sizeof(p + 1));//p + 1也是指针，指针就是地址，地址就是4/8个字节
//	printf("%d\n", sizeof(*p));//p表示的是字符串首字符的地址，（注：p虽然代表的是整个字符串的地址，但也是从首字符的地址开始的）解引用就是'a'，就是1个字节
//	printf("%d\n", sizeof(p[0]));//p[0] -- *(p + 0) -- 字符串首字符的地址的解引用，'a'，就是1个字节
//	printf("%d\n", sizeof(&p));//地址
//	printf("%d\n", sizeof(&p + 1));//地址
//	printf("%d\n", sizeof(&p[0] + 1));//地址
//
//	return 0;
//}


//int main()
//{
//	char* p = "abcdef";
//	printf("%d\n", strlen(p));
//	printf("%d\n", strlen(p + 1));
//	//printf("%d\n", strlen(*p));//崩
//	//printf("%d\n", strlen(p[0]));//崩
//	printf("%d\n", strlen(&p));//伪随机值，&p（二级地址）所访问的空间不是字符串abcdef，而是一级地址的空间
//	printf("%d\n", strlen(&p + 1));//伪随机值，按道理&p + 1要与上面的&p有关系，但是该二级地址所访问的空间是字符串的地址，也就是一级地址的空间，一级地址之间间距为4/8个字节，可能p与p + 1之间会有\0
//									//所以一级地址的长度之间就没有直接关系了
//	printf("%d\n", strlen(&p[0] + 1));
//
//	return 0;
//}


//int main()
//{
//	//二维数组的理解
//	//为了方便理解，才将二位数组理解为多行多列，实际上二位数组只有一行，多列
//	//当需要把二维数组理解为一行多列时，行数表示的是二维数组中一维数组的个数，列数代表的是一维数组中的元素个数
//	//所以二维数组有二维数组的数组名，二维数组中的一维数组也有自己的数组名，也就都可以使用数组名的意义
//	//注：二维数组的首元素是第一个一维数组，二维数组的元素的一维数组，一维数组的数组名是 ：二维数组数组名 + [i] 
//	
//
//	//对数组名arr的理解
////arr：数组名
////arr：该数组首元素的地址     ，除了两种特殊情况外
////sizeof(数组名)，arr是整个数组，计算的是整个数组的大小
////&数组名，取了整个数组的地址
//
//
//	int a[3][4] = { 0 };
//	printf("%d\n", sizeof(a));//二维数组数组名单独放在sizeof内部，计算的是整个二维数组的大小
//	printf("%d\n", sizeof(a[0][0]));//第一个一维数组的第一个元素的大小
//	printf("%d\n", sizeof(a[0]));//二维数组中的第一个一维数组数组名单独放在sizeof内部，计算的是整个第一个一维数组的大小
//	printf("%d\n", sizeof(a[0] + 1));//第一个一维数组没有单独放在sizeof内部，表示的是第一个一维数组首元素的地址
////地址+1，这里涉及到地址运算，步长的大小由地址本身的意义决定，这里的地址意义是一个一维数组的首元素，所以这里的步长为一个一维数组的元素，+1后地址在第一个一维数组的第二个元素
//	printf("%d\n", sizeof(*(a[0] + 1)));//第一个一维数组没有单独放在sizeof内部，所以a[0]表示第一个一维数组首元素的地址，地址运算后在第一个一维数组的第二个元素，再解引用，
//										//表示计算第一个一维数组的第二个元素的大小
//	printf("%d\n", sizeof(a + 1));//二维数组数组名没有单独放在sizeof内部，表示二维数组首元素的地址，也就是第一个一维数组的地址，+1后是第二个一维数组的地址
//	printf("%d\n", sizeof(*(a + 1)));//第二个一维数组的解引用，表示计算第二个一维数组的大小
//	printf("%d\n", sizeof(&a[0] + 1));//&a[0]表示整个第一个一维数组的地址，+1后是整个第二个一维数组的地址
//	printf("%d\n", sizeof(*(&a[0] + 1)));//将第二个一维数组的地址解引用，就是计算整个第二个一维数组的大小
//	printf("%d\n", sizeof(*a));//a也就是二维数组数组名没有单独放在sizeof内部，表示二维数组首元素的地址，而二维数组首元素就是第一个一维数组，再解引用，就是第一个一维数组的大小
//	printf("%d\n", sizeof(a[3]));//a[3]越界访问，但是不影响计算长度（因为这里没有越界访问的操作）
//	//a[3]是第四个一维数组数组名，单独放在sizeof内部，表示计算整个第四个一维数组的大小
//
//
//	return 0;
//}

//
//#include <stdio.h>
//int main()
//{
//	int a[5] = { 1, 2, 3, 4, 5 };
//	int* ptr = (int*)(&a + 1);//&a取的是整个数组的地址，+1后跳过了这一整个数组，地址在元素'5'的后面位置上，表示的是下一个数组的地址
//								//既然是下一个数组的地址，类型就是int (*)[5]，但是前面有强制力转换，该地址就变成了int*类型，就是普通指针类型
//								//所以不再表示整个下一个数组的地址，而表示当前元素的地址
//	printf("%d,%d", *(a + 1), *(ptr - 1));//ptr - 1，地址 - 1，此时地址意义为元素的地址，步长为一个元素，所以ptr - 1的地址是元素'5'的地址
//											//再解引用，就是元素'5'
//
//	return 0;
//}
//注：要注意地址的意义可以随着类型的改变而改变


//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结果是啥？
//struct Test
//{
//
//	int Num;
//	char* pcName;
//	short sDate;
//	char cha[2];
//	short sBa[4];
//}*p = (struct Test*)0x100000;//这边是结构体指针struct Test *p存储地址0x100000（因为0x100000被强制力转换为（struct Test*）类型）
//int main()
//{
//	printf("%p\n", p + 0x1);//p表示结构体指针，这里涉及到地址运算，这里地址的意义是结构体，所以+1（0x1）的步长是加了一个结构体，加了一个十进制的20字节
////而题目中所给结构体大小是20字节，属于十进制，p存放的是十六进制数据，且打印的占位符为%p，打印的是十六进制数据（因为编译给出的虚拟地址是十六进制的）
////所以需要进制转换，把十进制转换为十六进制，20 --> 14，所以结果是0x100014
//				
//	printf("%p\n", (unsigned long)p + 0x1);//p原来是结构体指针，类型是(struct Test*)，这里强制力转换为(unsigned long)，就不是指针了
//	//而是十六进制的(unsigned long)类型的数据，+1后就是0x100001
//	printf("%p\n", (unsigned int*)p + 0x1);//(struct Test*)强制力转换为(unsigned int*)类型的指针，地址意义改变，+ 0x1就是加上了一个地址的大小
//	//x86环境下是32位大小，地址大小是4个字节，所以加上了十六进制的4（注：地址本身就是十六进制数据），答案是0x100004
//	return 0;
//}
//总：类型的改变会改变指针的意义，也就是地址的意义，地址的运算的步长就会不同
	//类型的改变不会改变数据的进制类型



//#include <stdio.h>
//int main()
//{
//	int a[3][2] = { (0, 1), (2, 3), (4, 5) };//要识别二维数组的初始化
//	//二维数组分行初始化是{{0,1},{2,3},{4,5}}，用的是大括号，而这边用的是小括号，小括号内部用逗号隔开，运用到了逗号表达式
//	//逗号表达式是从左向右计算，最后一个表达式的结果是整个表达式的结果
//	//所以这边的初始化实际上是{{1,3},{5,0},{0,0}}，逗号表达式计算完只有3个元素了，剩下的元素位用0来补足
//	//所以这边的打印结果是1
//	int* p;
//	p = a[0];
//	printf("%d", p[0]);
//	return 0;
//}



//假设环境是x86环境，程序输出的结果是啥？
//#include <stdio.h>
//int main()
//{
//	int a[5][5];
//	int(*p)[4];//数组指针的创建，指针类型是int(*)[4]
//	p = a;//a是二维数组的数组名，表示的是二维数组首元素的地址，也就是二维数组第一个一维数组的地址，类型是int(*)[5]
//	//所以意思是把这个a这个二维数组的第一个一维数组的地址传给p存放，所以p地址的开始位置与二维数组a的首元素的地址一样，意味着他们记录的空间是一样的，可以相+-
//	//注：把int(*)[5]类型的数据传给int(*)[4]类型的数据，类型上有冲突，编译器会报警告
//	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
//	//数组名[i][j]是访问数组相应元素，&数组名[i][j]是取相应数组元素的地址
//	//指针名[i][j]是要转换进行理解的，指针名[i][j]--> *(*(指针 + i) + j)
//	//所以&指针名[i][j]--> &*(*(指针 + i) + j)
//
//	//所以这边涉及到指针运算，p指针类型是int(*)[4]，所以 +4（步长）表示跳过4个元素个数为4大小的一维数组，然后解引用，此时不是地址，指向的是第五个一维数组
//	//又相当于第五个一维数组的数组名，所以又变成了第五个一维数组首元素的地址，再+2，就是加两个元素的步长，跳过2个元素的地址，再解引用表示第五个一维数组的第3个元素
//	//然后再&，就又变成了第五个一维数组的第三个元素的地址
//	//%p打印地址，%d打印地址之间的差值，实际上就是元素个数的差
//	//所以%p --> -4按照地址的形式打印
//	// 先算出-4原码：10000000000000000000000000000100
//	// 但是地址的打印是补码表示的
//	// 所以反码：11111111111111111111111111111011
//	// 所以补码：11111111111111111111111111111100
//	// 编译器又是按照十六进制打印的：
//	// 所以二进制转换十六进制：FFFFFFFC
//	//所以%d --> -4
//	return 0;
//}
//总结：1，对指针运算的理解
		//指针的运算就是地址的运算，首先都要知道指针（地址）本身的意义，并且参照指针的类型明白指针的本身的意义，才能在+-整数运算中知道步长的大小
		//指针与指针的运算结果就是元素的个数，而不是地址的差值
//2，对指针名[i]的理解，指针名[i] == *(指针名 + i) == 整个解引用的结果的对象名（对象名是有意义的，比如当对象名为数组名时）
//3，对%p的理解
		//%p就是打印地址的，地址的打印可以先把十进制转换为二进制补码（地址是用补码表示的），再转换为十六进制（编译器进行观察）



//#include <stdio.h>
//int main()
//{
//	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//	int* ptr1 = (int*)(&aa + 1);//&aa表示取整个二维数组的地址，地址&aa类型是int（*）[2][5]，所以步长为一个二维数组，+1就是跳过一整个二维数组后的地址
//								//然后这边强制力类型转换为（int*），类型改变，地址意义改变，意义变为普通指针，步长变为一个元素
//								//然后赋给ptr1，ptr1 - 1就只减了1个元素的地址，再解引用，就是10
//								//或者这边不用强制力转换，也可以改变地址的意义，从而改变步长的大小，因为把&aa + 1这个地址传到ptr1后，ptr1的类型是int*，
//								//&aa + 1也会发生类型的转换，（int（*）[2][5]转换为int*）
//							//下面同理，所以两边的int*强制力类型转换是虚的
//
//	int* ptr2 = (int*)(*(aa + 1));//aa表示二维数组首元素的地址，就是第一个一维数组的地址，地址aa类型是int(*)[2]，所以此时步长是一个一维数组，+1表示跳过一个一维数组
//								//此时地址指向第二个一维数组的地址
//								//再解引用，相当于整个解引用结果的对象名，此时是第二个一维数组的数组名
//								//又强制力转换为（int*），类型改变，地址意义改变，步长变为一个元素，赋给了ptr2
//								//所以ptr2 - 1只减了一个元素的地址，结果就是5
//	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
//	return 0;
//}



//#include <stdio.h>
//int main()
//{
//	char* a[] = { "work","at","alibaba" };//指针数组，存放了3个元素，都是指针
//	char** pa = a;//a是指针数组名，表示指针数组首元素的地址，把这个地址存入pa二级指针中
//	pa++;//二级指针++，就是地址++，此时二级地址的意义是首元素，类型是char**，是普通指针，所以步长是一个元素，+1后就是第二个元素的地址
//	printf("%s\n", *pa);//把第二个元素的地址解引用，就是指针数组的第二个元素
//						//注：%s是打印字符串的，拿到字符串首字符的地址，从前向后打印，直到遇到\0停止打印
//							//打印的内容是取决于占位符的语法定义上的功能
//	return 0;
//}



//#include <stdio.h>
//int main()
//{
//	char* c[] = { "ENTER","NEW","POINT","FIRST" };//一级指针数组，4个元素，存放的是一级指针
//											
//	//注：上面的一级指针与下面的二级指针有关联，因为上面一级指针数组的数组名是下面二级指针数组的存放的元素
//
//	char** cp[] = { c + 3,c + 2,c + 1,c };//二级指针数组
//	char*** cpp = cp;//三级指针
//	printf("%s\n", **++cpp);
//	printf("%s\n", *-- * ++cpp + 3);//后面的涉及到空间内容的+运算，由图和代码可以得知：空间内容是指针，于是就是涉及到指针的运算
//	printf("%s\n", *cpp[-2] + 3);
//	printf("%s\n", cpp[-1][-1] + 1);
//	//注：上面代码中的计算会影响到地址，也可能会影响到空间中的内容（看解引用之后访问的如果是空间，并且继续进行了计算，就会影响到空间中的内容）
//	return 0;
//}
//总结：指针不仅是上一空间内容的地址，本身也是内容，需要空间存放
//每个空间下面都有对应的地址，每个地址又是指针，（指针）会被地址下的空间存放
//这题把空间和地址的图画好，明确空间与地址对应的一一关系，以及区分什么时候访问的是地址，什么时候访问的是空间，就可以搞定了
//其次注意++，--和+，-是不一样的，++，--会对变量自身产生改变，影响到下面的代码，因为++，--是变量 = 变量 + 1；，而+-只有在左边接收值的变量是自身，这个变量才会改变
//比如：
// int a = 5;
// int b = 6;
//int a = b + 4;	此时b本身没有改变，因为接收值的不是b变量
//b++；此时b改变，因为b是接收值的变量（即b = b + 1）
//int a = b++;此时b的值仍然发生了变化，因为这里有两个接收值的变量，一个是a，另一个是b 



